# algorithm/models/user_problem_recommendation.py
from math import exp
from django.db import models
from django.contrib.auth.models import User
from . import TagValue, UserTagMastery
from ..utils import mastery_level1, extract_algorithm_tag_indices
from users.models import User as CustomUser  # 自定义的User表
from tag.models import Tag, AlgorithmTag
from problems.models import Problem
from .user_comprehensive_level import UserComprehensiveLevel
from recommendation.models import SolvedProblem
import logging
logger = logging.getLogger('algorithm')

# 用户题目推荐模型
class UserProblemRecommendation(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    problem = models.ForeignKey(Problem, on_delete=models.CASCADE)
    recommendation_score = models.FloatField(default=0.0)

    class Meta:
        unique_together = ('user', 'problem')

    def __str__(self):
        return f"Recommendation for {self.user.username} on problem {self.problem.problem_number}: {self.recommendation_score}"
    # 备忘录模式

    def save_state(self):
        return {
            'problem_number': self.problem.problem_number,
            'recommendation_score': self.recommendation_score,
        }

    @staticmethod
    def restore_state(user, state_data_list):
        for state_data in state_data_list:
            problem = Problem.objects.get(problem_number=state_data['problem_number'])
            UserProblemRecommendation.objects.update_or_create(
                user=user,
                problem=problem,
                defaults={'recommendation_score': state_data['recommendation_score']}
            )

    @classmethod
    def create_or_update_problem_recommendation(cls, user_id, problem_number):
        try:
            # 获取用户实例
            user = User.objects.get(id=user_id)
            # 获取问题实例
            problem = Problem.objects.get(problem_number=problem_number)
            # 计算用户综合水平
            recommendation_score = cls.calculate_recommendation_score(user_id, problem_number)
            if recommendation_score is None:
                recommendation_score = 0.0  # 如果无法计算，则默认为0

            # 更新或创建推荐记录
            recommendation, created = cls.objects.update_or_create(
                user=user,
                problem=problem,
                defaults={
                    'recommendation_score': recommendation_score
                }
            )
            logger.debug(f"{cls.__name__}.create_or_update_problem_recommendation({user_id}, {problem_number})-->recommendation_score:{recommendation_score}")
            return recommendation, created
        except User.DoesNotExist:
            print(f"User with ID {user_id} does not exist.")
            logger.error(f"{cls.__name__}.create_or_update_problem_recommendation : User with ID {user_id} does not exist.")
            return None, False
        except Problem.DoesNotExist:
            print(f"Problem with Number {problem_number} does not exist.")
            logger.error(f"{cls.__name__}.create_or_update_problem_recommendation : Problem with Number {problem_number} does not exist.")
            return None, False
        except Exception as e:
            print(f"An error occurred while creating or updating recommendation: {str(e)}")
            logger.error(f"{cls.__name__}.create_or_update_problem_recommendation : {str(e)}")
            return None, False

    @classmethod
    def calculate_recommendation_score(cls, user_id, problem_number):
        try:
            # 获取用户和题目实例
            user = User.objects.get(id=user_id)
            problem = Problem.objects.get(problem_number=problem_number)

            # 获取用户的综合水平
            user_level_entry = UserComprehensiveLevel.objects.get(user=user)
            user_level = user_level_entry.user_level

            # 获取题目的难度
            problem_difficulty = problem.difficulty_index

            # 计算难度与用户水平的匹配比例
            # 使用正态分布函数来生成难度匹配的比例
            diff = abs(problem_difficulty - user_level)
            x = 0.5 + 0.5 * exp(-diff ** 2 / 2)  # 正态分布函数（模拟匹配程度，比例越接近1，越匹配）

            # 标签推荐分运算
            tag_recommendation_scores = []
            problem_tags = problem.tags.filter(index__in=AlgorithmTag.objects.values_list('index',flat=True)) # 获取题目关联的标签

            for tag in problem_tags:
                try:
                    recommendation_score = UserTagMastery.objects.get(user=user, tag=tag).recommendation_score
                    tag_recommendation_scores.append(recommendation_score)
                except (TagValue.DoesNotExist, UserTagMastery.DoesNotExist):
                    continue

            # 如果没有标签推荐分，则推荐分为0
            if not tag_recommendation_scores:
                return 0.0

            # 排序并计算标签推荐分的最小绝对差值
            tag_recommendation_scores.sort()
            if len(tag_recommendation_scores) == 0:
                min_diff = 0  # 当数组为空时，min_diff 赋值为 0
            elif len(tag_recommendation_scores) == 1:
                min_diff = tag_recommendation_scores[0]  # 当数组长度为 1 时，赋值为唯一的值
            else:
                min_diff = min(abs(tag_recommendation_scores[i] - tag_recommendation_scores[i + 1])
                               for i in range(len(tag_recommendation_scores) - 1))

            # 计算最终的推荐分
            recommendation_score = x * min_diff
            return recommendation_score

        except (User.DoesNotExist, Problem.DoesNotExist, UserComprehensiveLevel.DoesNotExist) as e:
            print(f"Error in fetching data: {str(e)}")
            return 0.0
        except Exception as e:
            print(f"An error occurred while calculating recommendation score: {str(e)}")
            return 0.0

    @classmethod
    def initialize_user_problem_recommendation_scores(cls, user_id):
        try:
            # 获取用户实例
            user = User.objects.get(id=user_id)
            # 获取用户已经解决的问题
            solved_problems = SolvedProblem.objects.filter(user=user)
            # 获取所有问题，排除那些 problem_number 在 solved_problems 列表中的问题
            unsolved_problems = Problem.objects.exclude(problem_number__in=[sp.problem_id for sp in solved_problems])

            # 对于每个未解决的问题，计算推荐分
            for problem in unsolved_problems:
                cls.create_or_update_problem_recommendation(user.id, problem.problem_number)
            print(f"recommendation_scores calculated for all unsolved_problems for user_id={user_id}")
            logger.info(f"{cls.__name__}.initialize_user_problem_recommendation_scores {user_id} Success")
        except User.DoesNotExist:
            print(f"No user found with id {user_id}")
            logger.error(f"{cls.__name__}.initialize_user_problem_recommendation_scores : No user found with id {user_id}")
            return None
        except Exception as e:
            print(f"An error occurred while calculating recommendation scores for user_id={user_id}: {e}")
            logger.error(f"{cls.__name__}.initialize_user_problem_recommendation_scores : An error occurred while calculating recommendation scores for user_id={user_id}: {e}")
            return None

    @classmethod
    def print_user_problem_recommendation_score_info(cls, user_id):
        try:
            user = User.objects.get(id=user_id)
            # 进行降序排序，并添加难度信息和标签
            problem_recommendation_scores = cls.objects.filter(user=user).order_by('-recommendation_score')

            print(f"Problem Recommendation info for user: {user.username}")
            print("------------------------------------------------")
            header = f"{'Problem Number':<15}{'Problem Name':<30}{'Difficulty':<20}{'Tags':<50}{'Recommendation Score':>20}"
            print(header)
            print('-' * len(header))

            for record in problem_recommendation_scores:
                # 获取算法标签列表，仅包括算法标签
                tag_names = ", ".join([tag.tag_name for tag in record.problem.tags.filter(
                    index__in=AlgorithmTag.objects.values_list('index', flat=True))])
                # 输出题目编号、名称、难度、标签和推荐分数
                print(
                    f"{record.problem.problem_number:<15}{record.problem.problem_name:<30}{record.problem.difficulty:<20}{tag_names:<50}{record.recommendation_score:>20.2f}")

        except User.DoesNotExist:
            print(f"No user found with ID {user_id}")
        except Exception as e:
            print(f"An error occurred: {str(e)}")

    @classmethod
    def update_user_problem_recommendation_score(cls, user_id, problems):
        try:
            # 更新流程：获取到新做的题目，获取到新做的题目数组中的所有标签
            # 更新带有标签数组中任一标签的题目的推荐分

            # 使用工具函数从问题集合中提取算法标签索引
            union_tag_indices = extract_algorithm_tag_indices(problems)

            problems_list = []

            for tag_index in union_tag_indices:
                # 查询与当前标签相关联的所有 Problem 实例
                tmp = list(Problem.objects.filter(tags__index=tag_index))
                # 将查询结果添加到 problems_list 中
                problems_list.extend(tmp)
            # 如果希望得到唯一的 Problem 实例，可以使用 set() 进行去重
            unique_problems_list = list(set(problems_list))

            for problem in unique_problems_list:
                cls.create_or_update_problem_recommendation(user_id, problem.problem_number)

            print(f"Successfully updated problem recommendation score for user_id={user_id}")
            logger.info(f"{cls.__name__}.update_user_problem_recommendation_score {user_id} Success")
        except Exception as e:
            print(f"An error occurred while updating problem recommendation score for user_id={user_id}: {e}")
            logger.info(f"{cls.__name__}.update_user_problem_recommendation_score : An error occurred while updating problem recommendation score for user_id={user_id}: {e}")

    @classmethod
    def get_top_two_recommended_problems(cls, user_id):
        try:
            user = User.objects.get(id=user_id)
        except User.DoesNotExist:
            return None, None  # 如果用户不存在，返回两个 None

        # 查询当前用户的所有推荐记录
        recommendations = cls.objects.filter(user=user)

        top_problem = None
        second_top_problem = None
        top_score = float('-inf')
        second_top_score = float('-inf')

        # 一次遍历找出最大和次大的推荐分数对应的问题
        for recommendation in recommendations:
            if recommendation.recommendation_score > top_score:
                second_top_problem = top_problem
                second_top_score = top_score
                top_problem = recommendation.problem
                top_score = recommendation.recommendation_score
            elif recommendation.recommendation_score > second_top_score:
                second_top_problem = recommendation.problem
                second_top_score = recommendation.recommendation_score

        return top_problem, second_top_problem
